/**
 * DataFlare API client for JavaScript
 */
class Client {
  /**
   * Create a new DataFlare client
   * @param {string} baseUrl - Base URL of the DataFlare API
   * @param {Object} options - Client options
   * @param {string} [options.apiKey] - API key for authentication
   * @param {number} [options.timeout=30000] - Request timeout in milliseconds
   * @param {Object} [options.headers] - Additional headers to include in requests
   */
  constructor(baseUrl, options = {}) {
    this.baseUrl = baseUrl.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl;
    this.apiKey = options.apiKey;
    this.timeout = options.timeout || 30000;
    this.headers = options.headers || {};
  }

  /**
   * Create a workflow
   * @param {Object} params - Workflow parameters
   * @param {string} params.name - Workflow name
   * @param {string} params.description - Workflow description
   * @param {string} params.definition - Workflow definition
   * @returns {Promise<Object>} Created workflow
   */
  async createWorkflow({ name, description, definition }) {
    return this._request('/api/workflows', {
      method: 'POST',
      body: JSON.stringify({
        name,
        description,
        definition,
      }),
    });
  }

  /**
   * Get a workflow
   * @param {string} workflowId - Workflow ID
   * @returns {Promise<Object>} Workflow
   */
  async getWorkflow(workflowId) {
    return this._request(`/api/workflows/${workflowId}`);
  }

  /**
   * Update a workflow
   * @param {string} workflowId - Workflow ID
   * @param {Object} params - Update parameters
   * @param {string} [params.description] - Workflow description
   * @param {string} [params.definition] - Workflow definition
   * @returns {Promise<Object>} Updated workflow
   */
  async updateWorkflow(workflowId, { description, definition } = {}) {
    const body = {};
    if (description !== undefined) body.description = description;
    if (definition !== undefined) body.definition = definition;

    return this._request(`/api/workflows/${workflowId}`, {
      method: 'PUT',
      body: JSON.stringify(body),
    });
  }

  /**
   * Delete a workflow
   * @param {string} workflowId - Workflow ID
   * @returns {Promise<void>}
   */
  async deleteWorkflow(workflowId) {
    return this._request(`/api/workflows/${workflowId}`, {
      method: 'DELETE',
    });
  }

  /**
   * List workflows
   * @param {Object} [params] - List parameters
   * @param {string} [params.nextToken] - Next token for pagination
   * @param {number} [params.limit] - Maximum number of workflows to return
   * @returns {Promise<Object>} List of workflows and next token
   */
  async listWorkflows({ nextToken, limit } = {}) {
    const query = new URLSearchParams();
    if (nextToken) query.append('nextToken', nextToken);
    if (limit) query.append('limit', limit.toString());

    const queryString = query.toString();
    const url = queryString ? `/api/workflows?${queryString}` : '/api/workflows';

    return this._request(url);
  }

  /**
   * Run a workflow
   * @param {string} workflowId - Workflow ID
   * @param {Object} [params] - Run parameters
   * @param {Object} [params.parameters] - Workflow parameters
   * @returns {Promise<Object>} Workflow run
   */
  async runWorkflow(workflowId, { parameters } = {}) {
    return this._request(`/api/workflows/${workflowId}/runs`, {
      method: 'POST',
      body: JSON.stringify({
        parameters: parameters || {},
      }),
    });
  }

  /**
   * Get a workflow run
   * @param {string} workflowId - Workflow ID
   * @param {string} runId - Run ID
   * @returns {Promise<Object>} Workflow run
   */
  async getWorkflowRun(workflowId, runId) {
    return this._request(`/api/workflows/${workflowId}/runs/${runId}`);
  }

  /**
   * List workflow runs
   * @param {string} workflowId - Workflow ID
   * @param {Object} [params] - List parameters
   * @param {string} [params.nextToken] - Next token for pagination
   * @param {number} [params.limit] - Maximum number of runs to return
   * @returns {Promise<Object>} List of workflow runs and next token
   */
  async listWorkflowRuns(workflowId, { nextToken, limit } = {}) {
    const query = new URLSearchParams();
    if (nextToken) query.append('nextToken', nextToken);
    if (limit) query.append('limit', limit.toString());

    const queryString = query.toString();
    const url = queryString
      ? `/api/workflows/${workflowId}/runs?${queryString}`
      : `/api/workflows/${workflowId}/runs`;

    return this._request(url);
  }

  /**
   * Cancel a workflow run
   * @param {string} workflowId - Workflow ID
   * @param {string} runId - Run ID
   * @returns {Promise<void>}
   */
  async cancelWorkflowRun(workflowId, runId) {
    return this._request(`/api/workflows/${workflowId}/runs/${runId}/cancel`, {
      method: 'POST',
    });
  }

  /**
   * Wait for a workflow run to complete
   * @param {string} workflowId - Workflow ID
   * @param {string} runId - Run ID
   * @param {Object} [options] - Wait options
   * @param {number} [options.timeout=60000] - Timeout in milliseconds
   * @param {number} [options.pollInterval=1000] - Poll interval in milliseconds
   * @returns {Promise<Object>} Workflow run
   */
  async waitForWorkflowRun(
    workflowId,
    runId,
    { timeout = 60000, pollInterval = 1000 } = {}
  ) {
    const startTime = Date.now();

    while (true) {
      const run = await this.getWorkflowRun(workflowId, runId);

      if (['completed', 'failed', 'cancelled'].includes(run.status)) {
        return run;
      }

      if (Date.now() - startTime > timeout) {
        throw new Error(`Workflow run ${runId} did not complete in time`);
      }

      await new Promise((resolve) => setTimeout(resolve, pollInterval));
    }
  }

  /**
   * Make a request to the API
   * @private
   * @param {string} path - API path
   * @param {Object} [options] - Request options
   * @returns {Promise<any>} Response data
   */
  async _request(path, options = {}) {
    const url = `${this.baseUrl}${path}`;
    const headers = {
      'Content-Type': 'application/json',
      ...this.headers,
    };

    if (this.apiKey) {
      headers.Authorization = `Bearer ${this.apiKey}`;
    }

    const fetchOptions = {
      method: options.method || 'GET',
      headers,
      signal: AbortSignal.timeout(this.timeout),
      ...options,
    };

    const response = await fetch(url, fetchOptions);

    if (response.status === 204) {
      return null;
    }

    if (response.status >= 200 && response.status < 300) {
      return response.json();
    }

    let errorData;
    try {
      errorData = await response.json();
    } catch (e) {
      throw new Error(`HTTP error ${response.status}: ${response.statusText}`);
    }

    const error = new Error(
      `${errorData.code || 'Error'}: ${errorData.message || 'Unknown error'}`
    );
    error.code = errorData.code;
    error.status = response.status;
    error.data = errorData;
    throw error;
  }
}

module.exports = Client;
